package guiserver;

import guicommon.controls.Control;
import guicommon.controls.handlers.MouseEventArgs;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import guicommon.Message;
import guicommon.controls.handlers.EventArgs;
import guicommon.controls.handlers.EventMessage;
import guicommon.gui.*;
import guicommon.session.*;
import java.nio.CharBuffer;
import java.util.concurrent.ConcurrentLinkedQueue;

/** Позволяет серверу работать с каждым из клиентов единообразно
 */
public class SingleClient {
    //Сокет этого клиента
    private Socket mySocket;
    //Поток ввода. В него приходят запросы от клиентов
    private BufferedReader input;
    //Поток вывода. В него сервер выводит ответы на запросы клиентов
    private PrintWriter output;
    //Поток, в котором происходит работа с клиентом
    private Thread myThread;
    //Статус подключения
    private boolean isConnected;
    //Примитив синхронизации
    private Lock lock;

    private SingleClient myInstance;

    private ConcurrentLinkedQueue<EventMessage> eventsQueue;

    //Конструктор (Получает ообъект-сокет)
    public SingleClient(Socket client) throws IOException {
        mySocket = client;
        input = new BufferedReader(new InputStreamReader(mySocket.getInputStream()));
        output = new PrintWriter(client.getOutputStream(), true);
        myThread = GetProcThread();
        lock = new ReentrantLock();
        isConnected = false;
        myInstance = this;
        eventsQueue = new ConcurrentLinkedQueue();
    }

    //Вызывает обработку этого клиента
    public void Connect() {
        lock.lock();
        try {
            isConnected = true;
            //запуск потока
            myThread.start();
        } finally {
            lock.unlock();
        }
    }

    //Останавливает обработку этого клиента
    public void Disconnect() throws IOException {
        lock.lock();
        try {
            //говорим, что клиент отключен
            isConnected = false;
        } finally {
            lock.unlock();
            //закрываем всё, что нужно закрыть
            input.close();
            output.close();
            mySocket.close();
            ControlsManager.GetInstance().CloseClient(myInstance);
        }
    }

    //Возвращает поток, в котором описана работа с данным клиентом
    private Thread GetProcThread() {
        return new Thread(new Runnable() {
            @Override
            public void run() {
                
                CharBuffer msgBuffer = CharBuffer.allocate(Message.MAX_MESSAGE_CAPACITY);

                while (isConnected) {
                    try {
                        lock.lock();
                        msgBuffer.clear();
                        msgBuffer = CharBuffer.allocate(Message.MAX_MESSAGE_CAPACITY);
                        msgBuffer.position(0);
                        Message msg = null;
                        //ждем, когда появится, что получить от клиента
                        if (!input.ready()) {
                            if (!eventsQueue.isEmpty()) {
                                msg = new EmptyControlMessage();
                            } else {
                                try {
                                    Thread.sleep(10);
                                } catch (InterruptedException ex) { }
                                continue;
                            }
                        }
                        if (msg == null) {
                            String line = input.readLine();
                            msgBuffer.put(line);
                            msgBuffer.position(0);
                        }
                        Message answer = null;
                        try {
                            if (msg == null) {
                                msg = Message.ParseMessage(msgBuffer);
                            }
                            System.out.printf("Received message %s", msg.GetCharSequence().toString());
                            System.out.println();
                            answer = Processing(msg);                            
                        } catch (Exception e) {
                            answer = new ErrorMessage(e.toString());
                        } finally {
                            msgBuffer.clear();
                            if (answer != null && answer.getClass() != EmptyControlMessage.class) {
                                System.out.printf("Sending message %s", answer.GetCharSequence().toString());
                                System.out.println();
                                output.println(answer.GetCharSequence().toString());
                            }
                            if (answer.GetMessageCode() == DisconnectMessage.ID) {
                                Disconnect();
                            }
                        }
                        
                    } catch (IOException ex) {
                        Logger.getLogger(SingleClient.class.getName()).log(Level.SEVERE, "Message processing error", ex);
                        try {
                            Disconnect();
                        } catch (IOException ex1) {
                            Logger.getLogger(SingleClient.class.getName()).log(Level.SEVERE, null, ex1);
                        }
                    } finally {
                        lock.unlock();
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException ex) { }
                    }
                }
            }

            private Message Processing(Message msg) {
                switch(msg.GetMessageType()){
                    case Session:
                        return ProcessingSessionMessage(msg);
                    case GUI:
                        if (msg.getClass().getSuperclass() == ControlMessage.class)
                            return ProcessingGUIMessage((ControlMessage)msg);
                        else
                            return ProcessingUpdateGUIMessage((UpdateMessage)msg);
                    case Handler:
                        return ProcessingHandlerMessage(msg);
                    default:
                        return new ErrorMessage(String.format("Udefined message type %d", msg.GetMessageType().GetIndex()));
                }
            }

            private Message ProcessingSessionMessage(Message msg) {
                int id = msg.GetMessageCode();
                if (ConnectMessage.ID == id) {
                    return new ConnectMessage();
                }
                if (DisconnectMessage.ID == id) {
                    return new DisconnectMessage();
                }
                return new ErrorMessage("Strange message received");
            }

            private Message ProcessingGUIMessage(ControlMessage msg) {
                if (msg.GetMessageCode() == EmptyControlMessage.ID) {
                    if (!eventsQueue.isEmpty()) {
                        return eventsQueue.remove();
                    }
                    return new EmptyControlMessage();
                }
                Control result = ControlsManager.GetInstance().RegisterControl(myInstance, msg);
                return new CallBackControlMessage(result.GetID());
            }

            private Message ProcessingHandlerMessage(Message msg) {
                //TODO
                return null;
            }

            private Message ProcessingUpdateGUIMessage(UpdateMessage updateMessage) {
                ControlsManager.GetInstance().UpdateControl(updateMessage);
                if (!eventsQueue.isEmpty()) {
                    return eventsQueue.remove();
                }
                return new EmptyControlMessage();
            }
        });
    }

    void RegisterEvent(Control selectedControl, EventArgs EventArgs) {
        EventMessage evtMsg = new EventMessage(selectedControl.GetID(), EventArgs);
        eventsQueue.add(evtMsg);       
    }
}
